﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Web;
using System.Web.SessionState;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Validation;
using BaoJianSoft.Common.Web;
using BaoJianSoft.RapidWeb.DynamicPages;
using BaoJianSoft.RapidWeb.DynamicPages.Configurations;
using BaoJianSoft.RapidWeb.Properties;
using BaoJianSoft.RapidWeb.WebResources;
using Newtonsoft.Json;

namespace BaoJianSoft.RapidWeb.Services
{
	/// <summary>
	/// HttpHandler to process request to dynamic web page.
	/// </summary>
	public class DynamicPageHandler : SupportDocumentReadyPage, IRequiresSessionState
	{
		private static readonly IPermissionBridge permissionBridge = SpringContext.Current.GetObject<IPermissionBridge>();
		private static readonly HashSet<string> ExcludedQueryStringNames = new HashSet<string> { "objectid", "stamp" };

		private const string NOCACHE_META = @"
				<meta http-equiv=""Expires"" content=""0"" ></meta>
				<meta http-equiv=""Cache-Control"" content=""no-cache""></meta>
				<meta http-equiv=""Pragma"" content=""no-cache""></meta>";

		private IDynamicPage dynamicPage;
		private Control controlContainer;

		/// <summary>
		/// Raises the System.Web.UI.Control.Init event to initialize the page.
		/// </summary>
		/// <param name="e">An System.EventArgs that contains the event data.</param>
		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);

			this.Initialize();

			HtmlGenericControl htmlTag = new HtmlGenericControl("html");
			htmlTag.Attributes["xmlns"] = "http://www.w3.org/1999/xhtml";
			this.Controls.Add(htmlTag);

			this.CreateHtmlHead(htmlTag);
			this.CreateHtmlBody(htmlTag);

			try
			{
				this.dynamicPage.OnInit(this, new EventArgs());
			}
			catch (ThreadAbortException)
			{
				throw;
			}
			catch (ValidationException exp)
			{
				this.ShowMesage(MessageTypes.Warn, exp.Message);
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				this.ShowMesage(MessageTypes.Warn, Resources.DP_UnknownErrorDetail);
			}
		}

		/// <summary>
		/// Raises the System.Web.UI.Page.LoadComplete event at the end of the page load stage.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnLoadComplete(EventArgs e)
		{
			base.OnLoadComplete(e);

			try
			{
				this.dynamicPage.OnLoad(this, new EventArgs());
			}
			catch (ThreadAbortException)
			{
				throw;
			}
			catch (ValidationException exp)
			{
				this.ShowMesage(MessageTypes.Warn, exp.Message);
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				this.ShowMesage(MessageTypes.Warn, Resources.DP_UnknownErrorDetail);
			}
		}

		/// <summary>
		/// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
		/// </summary>
		protected override void CreateChildControls()
		{
			base.CreateChildControls();
		}

		/// <summary>
		/// Raises the System.Web.UI.Control.PreRender event.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);

			try
			{
				if (this.dynamicPage.Configuration.JavaScriptUrls != null && this.dynamicPage.Configuration.JavaScriptUrls.Count > 0)
				{
					foreach (string javaScriptUrl in this.dynamicPage.Configuration.JavaScriptUrls)
						ClientScripts.RegisterBodyScriptInclude(VirtualPathUtility.ToAbsolute(javaScriptUrl));
				}

				this.dynamicPage.OnPreRender(this, new EventArgs());
			}
			catch (ThreadAbortException)
			{
				throw;
			}
			catch (ValidationException exp)
			{
				this.ShowMesage(MessageTypes.Warn, exp.Message);
			}
			catch (Exception exp)
			{
				Logger.Instance(this).Error(exp);
				this.ShowMesage(MessageTypes.Warn, Resources.DP_UnknownErrorDetail);
			}
		}

		/// <summary>
		/// The method is trying to register gridview panel resizing functionality at the end of window document.
		/// </summary>
		/// <param name="e"></param>
		protected override void OnPreRenderComplete(EventArgs e)
		{
			base.OnPreRenderComplete(e);
			this.CreateGlobalDynamicPageDataServicePostVariables();
			ClientScripts.RegisterStartupScript("window.RegisteredGridViewPanelObject.ResizeGridViewPanel();");
		}

		/// <summary>
		/// Initializes the System.Web.UI.HtmlTextWriter object and calls on the child controls of the System.Web.UI.Page to render.
		/// </summary>
		/// <param name="writer"></param>
		protected override void Render(HtmlTextWriter writer)
		{
			writer.Write(@"<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Transitional//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">");
			base.Render(writer);
		}

		/// <summary>
		/// The method is working on validating the permission and authentication of user to the page.
		/// </summary>
		private void Initialize()
		{
			try
			{
				this.dynamicPage = DynamicPageContext.Current.GetDynamicPage(QueryStringUtility.ObjectId, false);
				this.dynamicPage.ShowMessage += new Action<MessageTypes, string>(this.ShowMesage);
			}
			catch (ConfigurationErrorsException)
			{
				WebUtility.RedirectToPage(WebUtility.PageNotFoundUrl);
				return;
			}

			string permissionValue = this.dynamicPage.Configuration.PermissionValue;
			if (!permissionBridge.HasPermission(permissionValue))
			{
				WebUtility.RedirectToPage(WebUtility.NotAuthorizedUrl);
				return;
			}
		}

		private void ShowMesage(MessageTypes messageType, string message)
		{
			const string messageBoxJsTemplate = "window.RWD.MessageBox.$MessageType$('$MessageType$', '$MessageBody$');";
			string javaScriptBlock = messageBoxJsTemplate.Replace("$MessageType$", messageType.ToString()).Replace("$MessageBody$", message);
			ClientScripts.OnDocumentReady.Add2BeginOfBody(javaScriptBlock);
		}

		private void CreateHtmlHead(Control htmlContainer)
		{
			htmlContainer.Controls.Add(new HtmlHead { Title = this.dynamicPage.Configuration.Title });
			LiteralControl metaDefinition = new LiteralControl(NOCACHE_META);
			this.Header.Controls.Add(metaDefinition);

			// add style/script references.
			IWebResourceManager resourceManager = SpringContext.Current.GetObject<IWebResourceManager>();
			resourceManager.Flush("WebResources.DynamicPage");

			// initialize ExtJs cookie state and quick tips
			string javaScriptBlock = string.Format(CultureInfo.InvariantCulture, @"Ext.state.Manager.setProvider(new Ext.state.CookieProvider({{ path: '/{0}/' }})); Ext.QuickTips.init();", QueryStringUtility.ObjectId);
			ClientScripts.OnDocumentReady.Add2BeginOfBody(javaScriptBlock, JavaScriptPriority.High);
		}

		private void CreateHtmlBody(Control htmlContainer)
		{
			HtmlGenericControl htmlBody = new HtmlGenericControl("body");
			htmlBody.Attributes["class"] = "dynamicpagebody";
			htmlContainer.Controls.Add(htmlBody);

			HtmlForm htmlForm = new HtmlForm { ID = "DynamicPageForm" };
			htmlBody.Controls.Add(htmlForm);

			Panel pageContainer = new Panel { ID = "DynamicPageContainer", CssClass = "dynamicpage" };
			htmlForm.Controls.Add(pageContainer);

			this.controlContainer = new Panel { ID = "DynamicPageControlContainer", CssClass = "content" };
			pageContainer.Controls.Add(this.controlContainer);

			int renderedPanelCount = 0;
			foreach (BasePanelConfiguration basePanelConfiguration in this.dynamicPage.Configuration.Panels)
			{
				switch (basePanelConfiguration.PanelType)
				{
					case DynamicPagePanelTypes.ButtonPanel:
						this.CreateButtonPanel(basePanelConfiguration, renderedPanelCount++ == 0);
						break;

					case DynamicPagePanelTypes.GridViewPanel:
						this.CreateGridViewPanel(basePanelConfiguration, renderedPanelCount++ == 0);
						break;

					case DynamicPagePanelTypes.QueryPanel:
						this.CreateQueryPanel(basePanelConfiguration, renderedPanelCount++ == 0);
						break;
				}
			}
		}

		private void CreateQueryPanel(BasePanelConfiguration panelConfiguration, bool renderedAsFirstPanel)
		{
			QueryPanelConfiguration configuration = panelConfiguration as QueryPanelConfiguration;
			if (configuration != null)
			{
				Panel panel = new Panel { CssClass = "querypanel" };
				panel.Style["margin-top"] = renderedAsFirstPanel ? "2px" : "4px";

				this.controlContainer.Controls.Add(panel);

				QueryPanel queryPanel = new QueryPanel
				{
					ID = string.Format(CultureInfo.InvariantCulture, "QueryPanel_{0}_{1}", QueryStringUtility.ObjectId, configuration.Id), 
					Configuration = configuration 
				};

				panel.Controls.Add(queryPanel);
			}
		}

		private void CreateGridViewPanel(BasePanelConfiguration panelConfiguration, bool renderedAsFirstPanel)
		{
			GridViewPanelConfiguration configuration = panelConfiguration as GridViewPanelConfiguration;
			if (configuration != null)
			{
				Panel panel = new Panel { CssClass = "gridviewpanel" };
				panel.Style["margin-top"] = renderedAsFirstPanel ? "2px" : "4px";

				this.controlContainer.Controls.Add(panel);

				GridViewPanel gridViewPanel = new GridViewPanel
				{
					ID = string.Format(CultureInfo.InvariantCulture, "GridViewPanel_{0}_{1}", QueryStringUtility.ObjectId, configuration.Id),
					Configuration = configuration,
					AsUniqueGridView = true,
					DetailPanelPlugin = this.ResolveDetailPanelConfiguration(),
					AggregatePanelPlugin = ResolveAggregatePanelConfiguration()
				};

				panel.Controls.Add(gridViewPanel);
			}
		}

		private void CreateButtonPanel(BasePanelConfiguration panelConfiguration, bool renderedAsFirstPanel)
		{
			ButtonPanelConfiguration configuration = panelConfiguration as ButtonPanelConfiguration;
			if (configuration != null)
			{
				Panel panel = new Panel { CssClass = "buttonpanel" };
				panel.Style["margin-top"] = "1px";

				this.controlContainer.Controls.Add(panel);

				ButtonPanel buttonPanel = new ButtonPanel 
				{
					ID = string.Format(CultureInfo.InvariantCulture, "ButtonPanel_{0}_{1}", QueryStringUtility.ObjectId, configuration.Id),
					Configuration = configuration
				};

				panel.Controls.Add(buttonPanel);
			}
		}

		private void CreateGlobalDynamicPageDataServicePostVariables()
		{
			StringBuilder jsonStringBuilder = new StringBuilder();
			using (StringWriter stringWriter = new StringWriter(jsonStringBuilder))
			using (JsonTextWriter jsonTextWriter = new JsonTextWriter(stringWriter))
			{
				jsonTextWriter.WriteStartArray();

				// write ObjectId
				jsonTextWriter.WriteStartObject();
				jsonTextWriter.WritePropertyName("Name");
				jsonTextWriter.WriteValue("ObjectId");
				jsonTextWriter.WritePropertyName("Value");
				jsonTextWriter.WriteValue(QueryStringUtility.ObjectId);
				jsonTextWriter.WriteEndObject();

				foreach (string queryStringName in this.Request.QueryString.Keys)
				{
					if (string.IsNullOrEmpty(queryStringName)) continue;

					string queryStringValue = this.Request.QueryString[queryStringName];
					string lowerCasedQueryStringName = queryStringName.ToLowerInvariant().Trim();
					if (ExcludedQueryStringNames.Contains(lowerCasedQueryStringName)) continue;

					jsonTextWriter.WriteStartObject();
					jsonTextWriter.WritePropertyName("Name");
					jsonTextWriter.WriteValue(queryStringName);
					jsonTextWriter.WritePropertyName("Value");
					jsonTextWriter.WriteValue(queryStringValue);
					jsonTextWriter.WriteEndObject();
				}

				jsonTextWriter.WriteEndArray();
			}

			string javaScriptBlock = string.Format(CultureInfo.InvariantCulture, "window.GlobalDynamicPageDataServicePostVariables = {0};", jsonStringBuilder);
			const string globalDynamicPageDataServicePostVariablesTemplate = @"
				// add a method to get an value by key.
				window.GlobalDynamicPageDataServicePostVariables.GetItem = function(name)
				{
					for (i = 0; i < this.length; i++)
						if (this[i].Name == name) return this[i].Value;

					return null;
				}

				// remove the item with specified name
				window.GlobalDynamicPageDataServicePostVariables.RemoveItem = function(name)
				{
					var item = null;
					for (i = 0; i < this.length; i++)
					{
						if (this[i].Name == name) 
						{
							item = this[i];
							break;
						}
					}

					if (item != null) this.remove(item);
				}

				// add a method to check whether the key does exist in the array.
				window.GlobalDynamicPageDataServicePostVariables.Contains = function(name)
				{
					for (i = 0; i < this.length; i++)
						if (this[i].Name == name) return true;

					return false;
				}";

			ClientScripts.RegisterScriptBlock(javaScriptBlock + globalDynamicPageDataServicePostVariablesTemplate);
		}

		private GridViewPanelPluginConfiguration4DetailPanel ResolveDetailPanelConfiguration()
		{
			FormPanelConfiguration configuration = this.dynamicPage.Configuration.Panels.FirstOrDefault(p => p.PanelType == DynamicPagePanelTypes.DetailPanel) as FormPanelConfiguration;
			if (configuration == null)
				return new GridViewPanelPluginConfiguration4DetailPanel();

			return new GridViewPanelPluginConfiguration4DetailPanel
			{
				Draggable = configuration.Draggable,
				Resizable = configuration.Resizable,
				Height = configuration.Height,
				Width = configuration.Width,
				EditableHeaderText = configuration.HeaderText,
				ViewableHeaderText = configuration.HeaderText
			};
		}

		private GridViewPanelPluginConfiguration4AggregatePanel ResolveAggregatePanelConfiguration()
		{
			FormPanelConfiguration configuration = this.dynamicPage.Configuration.Panels.FirstOrDefault(p => p.PanelType == DynamicPagePanelTypes.AggregatePanel) as FormPanelConfiguration;
			if (configuration == null)
				return new GridViewPanelPluginConfiguration4AggregatePanel();

			return new GridViewPanelPluginConfiguration4AggregatePanel
			{
				Draggable = configuration.Draggable,
				Resizable = configuration.Resizable,
				Height = configuration.Height,
				Width = configuration.Width,
				HeaderText = configuration.HeaderText
			};
		}
	}
}
